package com.jsh.erp.logger.aspect;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import com.alibaba.fastjson.JSON;
import com.jsh.erp.logger.annotation.PLog;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.Temporal;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;

/**
 * @author tavion
 * @date 2019/11/28/
 * @time 5:11:27
 */
@Aspect
@Configuration
public class PrintLogAspect {
    private static final Logger logger =
        ((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger(PrintLogAspect.class.getName());

    @Around("@annotation(pLog)")
    public Object doAroundController(ProceedingJoinPoint pjp, PLog pLog) throws Throwable {
        Instant startInstant = Instant.now();
        Object responseObject = null;

        boolean enableLogger=loggerEnabled(pLog);
        try {
            if(enableLogger){
                this.logRequest(pjp,pLog);
            }
            responseObject = pjp.proceed();
        } catch (Exception e) {
            throw e;
        } finally {
            if(enableLogger){
                long execTm = durationMillis(startInstant, Instant.now());
                this.logResponse(pjp, pLog,responseObject, execTm);
            }
        }
        return responseObject;
    }


    private boolean loggerEnabled(PLog lemonPrintLog){
        return lemonPrintLog.value().getCode().isGreaterOrEqual(logger.getEffectiveLevel());
    }

    private void printLog(PLog lemonPrintLog,String format,Object ... args){
        Level level= lemonPrintLog.value().getCode();

        switch (level.toInt()){
            case Level.TRACE_INT:
                logger.trace(format,args);
                break;
            case Level.DEBUG_INT:
                logger.debug(format,args);
                break;
            case Level.INFO_INT:
                logger.info(format,args);
                break;
            case Level.WARN_INT:
                logger.warn(format,args);
                break;
            case Level.ERROR_INT:
                logger.error(format,args);
                break;
        }

    }

    private void logRequest(ProceedingJoinPoint pjp,PLog lemonPrintLog) {
        try {
            String className = pjp.getTarget().getClass().getName();
            String methodName = pjp.getSignature().getName();

            String data = Optional.ofNullable(pjp.getArgs()).map(args -> Stream.of(args).map(this::toJsonString).collect(Collectors.joining("~"))).orElse("");
            printLog(lemonPrintLog,"{} 输入参数:\n{}", className + "#" + methodName, data);
        } catch (Exception t) {
            logger.error("log request fail", t);
        }
    }

    private void logResponse(ProceedingJoinPoint pjp,PLog lemonPrintLog,Object result, long execTm) {
        try {
            String className = pjp.getTarget().getClass().getName();
            String methodName = pjp.getSignature().getName();

            String data = Optional.ofNullable(result).map(this::toJsonString).orElse(null);
            if (data != null) {
                printLog(lemonPrintLog,"{} 执行时间:{}\n输出返回:\n{}", className + "#" + methodName, execTm, data);
            } else {
                printLog(lemonPrintLog,"{} 执行时间:{}", className + "#" + methodName, execTm);
            }

        } catch (Exception t) {
            logger.error("log response fail", t);
        }
    }

    private String toJsonString(Object object) {
        if (null == object) {
            return null;
        }
        try {
            return JSON.toJSONString(object, true);
        } catch (Exception e) {
            logger.error("Print Json Object fail!",e);
            return null;
        }
    }


    public static long durationMillis(Temporal var0, Temporal var1) {
        return Duration.between(var0, var1).toMillis();
    }

}
